package concurrecy;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class Fair {
    private int x = 0;
    private int y = 0;

    public Fair() {}
    public Fair(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public int getX() {
        return x;
    }

    public int getY() {
        return y;
    }

    public void incrementX() { x ++;}
    public void incrementY() { y ++;}

    public void check() {
        if(x != y)
            throw new RuntimeException("x != y, x = " + x + ", y = " + y);
    }
}

abstract class FairManager {
    protected Fair fair = new Fair();
    public AtomicInteger count = new AtomicInteger(0);
    List<Fair> list = Collections.synchronizedList(new ArrayList<>());

    public void store(Fair fair) {
        list.add(fair);
    }

    public synchronized Fair getFair() {
        return new Fair(fair.getX(), fair.getY());
    }

    @Override
    public String toString() {
        return this.getClass().getSimpleName() + " : " + count.get();
    }

    public abstract void increment();

    static class FairManager1 extends FairManager {
        @Override
        public synchronized void increment() {
            fair.incrementX();
            fair.incrementY();
            store(fair);
        }
    }

    static class FairManager2 extends FairManager {
        @Override
        public void increment() {
            synchronized (this) {
                fair.incrementX();
                fair.incrementY();
            }
            store(fair);
        }
    }

    static class FairManager3 extends FairManager {
        private Lock lock = new ReentrantLock();
        @Override
        public void increment() {
            lock.lock();
            try {
                fair.incrementX();
                fair.incrementY();
                store(fair);
            } finally {
                lock.unlock();
            }
        }

        @Override
        public Fair getFair() {
            lock.lock();
            try {
                return new Fair(fair.getX(), fair.getY());
            } finally {
                lock.unlock();
            }
        }
    }

    static class FairManager4 extends FairManager {
        private Lock lock = new ReentrantLock();
        @Override
        public void increment() {
            lock.lock();
            try {
                fair.incrementX();
                fair.incrementY();
            } finally {
                lock.unlock();
            }
            store(fair);
        }

        @Override
        public Fair getFair() {
            lock.lock();
            try {
                return new Fair(fair.getX(), fair.getY());
            } finally {
                lock.unlock();
            }
        }
    }
}

public class FairDemo {

    static void check(FairManager fairManager) {
        fairManager.getFair().check();
        fairManager.count.incrementAndGet();
    }

    public static void main(String[] args) throws InterruptedException {
        FairManager fairManager1 = new FairManager.FairManager1();
        FairManager fairManager2 = new FairManager.FairManager2();
        FairManager fairManager3 = new FairManager.FairManager3();
        FairManager fairManager4 = new FairManager.FairManager4();

        ExecutorService service = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            service.execute(() -> {
                while(true)
                    fairManager1.increment();
            });
            service.execute(() -> {
                while(true)
                    fairManager2.increment();
            });
            service.execute(() -> {
                while(true)
                    fairManager3.increment();
            });
            service.execute(() -> {
                while(true)
                    fairManager4.increment();
            });

            service.execute(() -> {
                while(true)
                    check(fairManager1);
            });
            service.execute(() -> {
                while(true)
                    check(fairManager2);
            });
            service.execute(() -> {
                while(true)
                    check(fairManager3);
            });
            service.execute(() -> {
                while(true)
                    check(fairManager4);
            });
        }

        TimeUnit.SECONDS.sleep(5);
        System.out.println(fairManager1);
        System.out.println(fairManager2);
        System.out.println(fairManager3);
        System.out.println(fairManager4);

        System.exit(0);
    }
}
